package com.jojo.jojomall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jojo.jojomall.product.service.CategoryBrandRelationService;
import com.jojo.jojomall.product.vo.Catelog2Vo;
import org.aspectj.lang.annotation.Around;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jojo.common.utils.PageUtils;
import com.jojo.common.utils.Query;

import com.jojo.jojomall.product.dao.CategoryDao;
import com.jojo.jojomall.product.entity.CategoryEntity;
import com.jojo.jojomall.product.service.CategoryService;
import org.springframework.util.StringUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
    RedissonClient redissonClient;
    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;
    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listTree() {
        //1查出所有分类：
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
        List<CategoryEntity> level1Menus = categoryEntities.stream()
                .filter((categoryEntitie) -> categoryEntitie.getParentCid() == 0)
                .map(menu->{menu.setChildren(getChildren(menu,categoryEntities));return menu;})
                .sorted((m1,m2)->(m1.getSort()==null?0: m1.getSort())-(m2.getSort()==null?0: m2.getSort()))
                .collect(Collectors.toList());
        return categoryEntities;
        //2组装成父子的树结构
    }

    @Override
    public void removeMenyByids(List<Long> asList) {
        //TODO
        baseMapper.deleteBatchIds(asList);
    }

    @Override
    public Long[] findPathById(Long categlogId) {
        ArrayList<Long> paths = new ArrayList<>();
        List<Long> parentPathById = findParentPathById(categlogId, paths);
       return (Long[]) parentPathById.toArray();
    }
//    @CacheEvict(value = "category",allEntries = true); 这个注解会清空所用这个分组下的缓存

//由于要关系到关系表的修改，所以在这里详写一下；  //使用@Caching可以加入多个失效注解可以让多个注解实现缓存清除
    @Caching(evict = {@CacheEvict(value = "category",key = "getLevel1Categorys")
            ,@CacheEvict(value = "category",key ="getCatalogJson")
    })
    @Override
    public void updateDetail(CategoryEntity category) {
        //老样子自己先改自己的
        updateById(category);
        //然后要改正关联表的
        //首先照例判断是否空了
        if (!StringUtils.isEmpty(category.getName())){
            //TODO 更新其他关联信息
        categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
        }

    }
/**
 * @Author 小夫
 * @Description  获取所有一级分类
 * @Date 9:18 2022/5/12
 * @Param []
 * @return java.util.List<com.jojo.jojomall.product.entity.CategoryEntity>
 **/
//每一个需要缓存的数据，我们都要来指定要放到哪个名字的缓存（缓存的分区（））
@Cacheable(value = {"category"},key = "#root.methodName") //代表当前结果需要缓存，如果缓存中有，方法不再调用，如果缓存中没有，调用方法，最后将结果方法放回缓存
@Override
    public List<CategoryEntity> getLevel1Categorys() {
//        利用一级分类父id等于0，查询到集合并返回；
    System.out.println("这个方法被调用了");
        List<CategoryEntity> categoryEntities = baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, 0).orderByAsc(CategoryEntity::getCatId));
        return categoryEntities;
    }
    /**
     * @Author 小夫
     * @Description  查出分类
     * @Date 15:53 2022/5/12
     * @Param []
     * @return java.util.Map<java.lang.String,java.lang.Object>
     **/
    @Cacheable(value = {"category"},key = "#root.methodName")
    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
        //        第一步找到所有的一级分类
        List<CategoryEntity> level1Categorys = getLevel1Categorys();

        Map<String, List<Catelog2Vo>> collect = level1Categorys.stream().collect(Collectors.toMap(l1 -> l1.getCatId().toString(), v1 -> {
//            每个类的一级分类，我们要查到这个类的二级分类；
            List<CategoryEntity> categoryEntities = baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, v1.getCatId()));
//            拿到所有父类分类id为这个的所有二级分类后，我们把这个集合给我们的List<VO>
            List<Catelog2Vo> catelog2Vos = null;
            if (categoryEntities != null) {
                catelog2Vos = categoryEntities.stream().map(lv2 -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo(v1.getCatId().toString()/*一级分类id*/, null, lv2.getCatId().toString(), lv2.getName());
                    //                    找level3的东西并封装
                    List<CategoryEntity> categoryEntities3 = baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, lv2.getCatId()));
                    if (categoryEntities3 != null) {
                        List<Catelog2Vo.Catelog3VO> VO3collect1 = categoryEntities3.stream().map(lv3 -> {
                            Catelog2Vo.Catelog3VO catelog3VO = new Catelog2Vo.Catelog3VO(lv2.getCatId().toString(), lv3.getCatId().toString(), lv3.getName());
                            return catelog3VO;
                        }).collect(Collectors.toList());
                        catelog2Vo.setCatelog3List(VO3collect1);
                    }
                    return catelog2Vo;
                }).collect(Collectors.toList());
            }
            return catelog2Vos;
        }));
        return collect;

    }

    public Map<String, List<Catelog2Vo>> getCatalogJsonOld() {
        /*1,空结果缓存，： 解决缓存穿透
         * 2，设置过期时间（加随机值）：（尤其是加随机值），预防缓存雪崩
         * 3，加锁：解决缓存击穿；
         * */


        //TODO 加入缓存逻辑
        ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
        String catelogyJson = opsForValue.get("catalogJson");
//        看缓存里有没有
        if (StringUtils.isEmpty(catelogyJson)){
            System.out.println("缓存未命中，。。。-》查询数据库");
            Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDbWithRedisLockPlus();
            return catalogJsonFromDb;
        }
        System.out.println("查询命中。。。。。");
//        缓存里有就继续；
        String catalogJson = opsForValue.get("catalogJson");
        Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
        });
        return result;
    }
    /**
     * @Author 小夫
     * @Description  分布式锁升级版 Redisson版
     * @Date 8:25 2022/5/16
     * @Param []
     * @return java.util.Map<java.lang.String,java.util.List<com.jojo.jojomall.product.vo.Catelog2Vo>>
     **/
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLockPlus() {
//        锁的名字 粒度 。越细越快；
//        锁的粒度：具体缓存的是
        RLock lock = redissonClient.getLock("CataloglodJSon-lock");
        lock.lock();
        Map<String, List<Catelog2Vo>> datfromDb ;
        try {
            datfromDb = getDatfromDb();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }


        return null;
    }



/**
 * @Author 小夫
 * @Description  用redis分布式锁 lock
 * @Date 15:28 2022/5/14
 * @Param []
 * @return java.util.Map<java.lang.String,java.util.List<com.jojo.jojomall.product.vo.Catelog2Vo>>
 **/
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
//先去redis”占坑“
        //            设置锁的过期时间，防止因为某些程序问题而形成死锁  注意设置过期时间必须是和加锁是
//            原子的，
        String uidtocken = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uidtocken,30,TimeUnit.SECONDS);
//        判断有没有站到坑位，
        if (lock){
//            加锁(并加上过期时间)成功，开始操作并返回
            Map<String, List<Catelog2Vo>> datfromDb = getDatfromDb();
//            删除掉锁，”离开坑位“, 因该确保删除掉的是自己设置的锁，所以我们加入了uuid系列，再去查询下数据库；
            String script="if redis.call(\"get\", KEYS[1]) == ARGV[1] then\n" +
                    "\n" +
                    "return redis.call(\"del\", KEYS[1])\n" +
                    "\n" +
                    "else\n" +
                    "\n" +
                    "return 0\n" +
                    "\n" +
                    "end";
            Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class)
                    , Arrays.asList("lock")
                    , uidtocken);/*进行删除锁*/
//       因为要保证原子性，防止时间误差，所以还是使用上面的原子行lua脚本；
//            if (uidtocken.equals(redisTemplate.opsForValue().get("lock"))){
//                redisTemplate.delete("lock");
//            }
            return datfromDb;
        }else {
            /*枷锁失败，等待100ms后重试*/

            return getCatalogJsonFromDbWithRedisLock();/*自旋的方式重试*/

        }

    }

    private Map<String, List<Catelog2Vo>> getDatfromDb() {
        ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
        String catelogyJson = opsForValue.get("catalogJson");
        if (!StringUtils.isEmpty(catelogyJson)){
            String catalogJson = opsForValue.get("catalogJson");
            Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
            });
            return result;
        }

//        第一步找到所有的一级分类
        List<CategoryEntity> level1Categorys = getLevel1Categorys();

        Map<String, List<Catelog2Vo>> collect = level1Categorys.stream().collect(Collectors.toMap(l1 -> l1.getCatId().toString(), v1 -> {
//            每个类的一级分类，我们要查到这个类的二级分类；
            List<CategoryEntity> categoryEntities = baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, v1.getCatId()));
//            拿到所有父类分类id为这个的所有二级分类后，我们把这个集合给我们的List<VO>
            List<Catelog2Vo> catelog2Vos = null;
            if (categoryEntities != null) {
                catelog2Vos = categoryEntities.stream().map(lv2 -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo(v1.getCatId().toString()/*一级分类id*/, null, lv2.getCatId().toString(), lv2.getName());
                    //                    找level3的东西并封装
                    List<CategoryEntity> categoryEntities3 = baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, lv2.getCatId()));
                    if (categoryEntities3 != null) {
                        List<Catelog2Vo.Catelog3VO> VO3collect1 = categoryEntities3.stream().map(lv3 -> {
                            Catelog2Vo.Catelog3VO catelog3VO = new Catelog2Vo.Catelog3VO(lv2.getCatId().toString(), lv3.getCatId().toString(), lv3.getName());
                            return catelog3VO;
                        }).collect(Collectors.toList());
                        catelog2Vo.setCatelog3List(VO3collect1);
                    }
                    return catelog2Vo;
                }).collect(Collectors.toList());
            }
            return catelog2Vos;
        }));
        String s = JSON.toJSONString(collect);
        opsForValue.set("catalogJson",s,1, TimeUnit.DAYS);
        return collect;
    }

    /**
 * @Author 小夫
 * @Description  封装一个查询数据库的方法
 * @Date 9:49 2022/5/14
 * @Param []
 * @return java.util.Map<java.lang.String,java.util.List<com.jojo.jojomall.product.vo.Catelog2Vo>>
 **/
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithLocalLock() {
        //为了解决缓存穿透我们要保证每次有东西查询一样东西查询数据库先让他一个人查，
        // 查完能存缓存，其他人不会都来访问数据库
//        用同一把锁，锁住需要这个锁的所有线程；
//        1，synchronizer:(this) spirngboot 所有的组件都是单例的；

synchronized (this) {
    System.out.println("查询了数据库");
//    有下一个人得到锁以后，我们应该再去缓存中再看一次，没有才需要继续查询
    ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
    String catelogyJson = opsForValue.get("catalogJson");
    if (!StringUtils.isEmpty(catelogyJson)){
        String catalogJson = opsForValue.get("catalogJson");
        Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
        });
        return result;
    }

//        第一步找到所有的一级分类
    List<CategoryEntity> level1Categorys = getLevel1Categorys();

    Map<String, List<Catelog2Vo>> collect = level1Categorys.stream().collect(Collectors.toMap(l1 -> l1.getCatId().toString(), v1 -> {
//            每个类的一级分类，我们要查到这个类的二级分类；
        List<CategoryEntity> categoryEntities = baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, v1.getCatId()));
//            拿到所有父类分类id为这个的所有二级分类后，我们把这个集合给我们的List<VO>
        List<Catelog2Vo> catelog2Vos = null;
        if (categoryEntities != null) {
            catelog2Vos = categoryEntities.stream().map(lv2 -> {
                Catelog2Vo catelog2Vo = new Catelog2Vo(v1.getCatId().toString()/*一级分类id*/, null, lv2.getCatId().toString(), lv2.getName());
                //                    找level3的东西并封装
                List<CategoryEntity> categoryEntities3 = baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, lv2.getCatId()));
                if (categoryEntities3 != null) {
                    List<Catelog2Vo.Catelog3VO> VO3collect1 = categoryEntities3.stream().map(lv3 -> {
                        Catelog2Vo.Catelog3VO catelog3VO = new Catelog2Vo.Catelog3VO(lv2.getCatId().toString(), lv3.getCatId().toString(), lv3.getName());
                        return catelog3VO;
                    }).collect(Collectors.toList());
                    catelog2Vo.setCatelog3List(VO3collect1);
                }
                return catelog2Vo;
            }).collect(Collectors.toList());
        }
        return catelog2Vos;
    }));
    String s = JSON.toJSONString(collect);
    opsForValue.set("catalogJson",s,1, TimeUnit.DAYS);
    return collect;
}
    }

    //封装一个递归方法，用来层层寻找
    public List<Long> findParentPathById(Long categlogId,List<Long> paths) {
        /*首先传入要查的id和地址列表*/
        /*然后增加上这个本来的额*/
        paths.add(categlogId);
        /*拿到对象并开始查找*/
        CategoryEntity byId = this.getById(categlogId);
        if (byId.getParentCid()!=0){
//            父与递归查询，直到找不到
            findParentPathById(byId.getParentCid(),paths);
        }
//        返回地址
        return paths;


    }



    /*定义一个方法去获得子菜单*/

    /**
     *
     * @param root 当前要加入孩子的根分叉
     * @param all
     * @return
     */
    private List<CategoryEntity> getChildren(CategoryEntity root,List<CategoryEntity> all){
    List<CategoryEntity> collect = all.stream()
            .filter(ca -> ca.getParentCid().equals(root.getCatId()))
            .map(m -> {
                m.setChildren(getChildren(m, all));
                return m;
            })
            .sorted((m1,m2)->(m1.getSort()==null?0: m1.getSort())-(m2.getSort()==null?0: m2.getSort()))
            .collect(Collectors.toList());
    return collect;
}
}
